package com.jprocms.module.cms.controller.portal.api;

import com.jpro.framework.common.pojo.CommonResult;
import com.jpro.framework.common.pojo.PageResult;
import com.jpro.framework.security.core.util.SecurityFrameworkUtils;
import com.jprocms.module.cms.controller.admin.vote.vo.VoteListReqVO;
import com.jprocms.module.cms.controller.admin.vote.vo.VoteRespVO;
import com.jprocms.module.cms.controller.directive.VoteListDirective;
import com.jprocms.module.cms.convert.vote.VoteConvert;
import com.jprocms.module.cms.dal.dataobject.site.SiteDO;
import com.jprocms.module.cms.dal.dataobject.vote.VoteDO;
import com.jprocms.module.cms.dal.dataobject.voteitem.VoteItemDO;
import com.jprocms.module.cms.enums.EnumFrontOperate;
import com.jprocms.module.cms.enums.EnumFrontOperateQueryType;
import com.jprocms.module.cms.service.operate.OperateService;
import com.jprocms.module.cms.service.vote.VoteService;
import com.jprocms.module.cms.util.CmsContextUtil;
import com.jprocms.module.cms.util.QueryUtils;
import com.jprocms.module.cms.util.RequestUtils;
import com.jprocms.module.cms.web.Directives;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.time.OffsetDateTime;
import java.util.*;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

import static com.jpro.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.jprocms.module.cms.constant.CmsSysConstants.FRONT_API;
import static com.jprocms.module.cms.constant.VoteConstants.*;
import static com.jprocms.module.cms.enums.ErrorCodeConstants.*;

/**
 * 投票前台 接口
 *
 * @author jprocms
 */
@Tag(name = "VoteController", description = "投票接口")
@RestController
@RequestMapping({FRONT_API + "/vote"})
public class VoteApiController {
    private final VoteService service;
    private OperateService operateService;

    public VoteApiController(OperateService operateService, VoteService service) {
        this.operateService = operateService;
        this.service = service;
    }

    @Operation(summary = "获取投票列表（VoteList标签）")
    @Parameter(in = ParameterIn.QUERY, name = "siteId", description = "站点ID。默认为当前站点",
            schema = @Schema(type = "integer", format = "int32"))
    @Parameter(in = ParameterIn.QUERY, name = "isWithinDate", description = "是否在投票期限内。如：`true` `false`",
            schema = @Schema(type = "boolean"))
    @Parameter(in = ParameterIn.QUERY, name = "isAllSite", description = "是否获取所有站点投票。如：`true` `false`，默认`false`",
            schema = @Schema(type = "boolean"))
    @Parameter(in = ParameterIn.QUERY, name = "offset", description = "从第几条数据开始获取。默认为0，即从第一条开始获取",
            schema = @Schema(type = "integer", format = "int32"))
    @Parameter(in = ParameterIn.QUERY, name = "limit", description = "共获取多少条数据。最大不能超过1000",
            schema = @Schema(type = "integer", format = "int32"))
    @GetMapping
    public CommonResult<List<VoteRespVO>> list(HttpServletRequest request) {
        return query(request, (args, params) -> {
            int offset = Directives.getOffset(params);
            int count = Directives.getCount(params);
            args.setOffset(offset);
            args.setCount(count);
            List<VoteDO> voteList = service.getVoteList(args);
            return CommonResult.success(VoteConvert.INSTANCE.convertList(voteList));
        });
    }

    @Operation(summary = "获取投票分页（VotePage标签）")
    @Parameter(in = ParameterIn.QUERY, name = "siteId", description = "站点ID。默认为当前站点",
            schema = @Schema(type = "integer", format = "int32"))
    @Parameter(in = ParameterIn.QUERY, name = "isWithinDate", description = "是否在投票期限内。如：`true` `false`",
            schema = @Schema(type = "boolean"))
    @Parameter(in = ParameterIn.QUERY, name = "isAllSite", description = "是否获取所有站点投票。如：`true` `false`，默认`false`",
            schema = @Schema(type = "boolean"))
    @Parameter(in = ParameterIn.QUERY, name = "page", description = "第几页",
            schema = @Schema(type = "integer", format = "int32"))
    @Parameter(in = ParameterIn.QUERY, name = "pageSize", description = "每页多少条数据。最大不能超过1000",
            schema = @Schema(type = "integer", format = "int32"))
    @GetMapping("/page")
    public CommonResult<PageResult<VoteRespVO>> page(HttpServletRequest request) {
        return query(request, (args, params) -> {
            int page = Directives.getPage(params);
            int pageSize = Directives.getPageSize(params);
            args.setPageNo(page);
            args.setPageSize(pageSize);
            PageResult<VoteDO> votePage = service.getVotePage(args);
            return CommonResult.success(VoteConvert.INSTANCE.convertPage(votePage));
        });
    }


    private <T> T query(HttpServletRequest request, BiFunction<VoteListReqVO, Map<String, String>, T> handle) {
        SiteDO site = CmsContextUtil.getSite();
        VoteListReqVO args = new VoteListReqVO();
        Map<String, String> params = QueryUtils.getParams(request.getQueryString());
        VoteListDirective.assemble(args, params, site.getId());
        return handle.apply(args, params);
    }

    @Operation(summary = "获取投票对象（Vote标签）")
    @ApiResponses(value = {@ApiResponse(description = "投票对象")})
    @GetMapping("/{id:[\\d]+}")
    public CommonResult<VoteRespVO> getDetail(@Parameter(description = "投票ID") @PathVariable Long id) {
        VoteDO vote = service.getVote(id);
        validateVote(id, vote);
        return CommonResult.success(VoteConvert.INSTANCE.convert(vote));
    }

    public static void validateVote(Long id, VoteDO vote) {
        if (vote == null) {
            throw exception(VOTE_NOT_EXISTS);
        }
        if (Boolean.FALSE.equals(vote.getEnabled())) {
            throw exception(VOTE_NOT_ENABLE);
        }
    }

    @Operation(summary = "投票")
    @ApiResponse(description = "msg: 提示信息。code: 状态码。0，成功；其他失败，失败消息在msg")
    @PostMapping("/vote")
    public CommonResult<Boolean> vote(@RequestBody @Valid VoteSubmitDTO params,
                                      HttpServletRequest request, HttpServletResponse response) {
        List<Long> optionIds = params.getItemIds();
        VoteDO vote = service.getVote(params.id);
        if (vote == null) {
            throw exception(VOTE_NOT_EXISTS);
        }
        validateVote(vote, optionIds);
        switch (vote.getStatus()) {
            case STATUS_DISABLED:
                throw exception(VOTE_NOT_ENABLE);
            case STATUS_NOT_STARTED:
                throw exception(VOTE_NOT_START);
            case STATUS_HAS_ENDED:
                throw exception(VOTE_HAS_ENDED);
            default:
        }
        // 只能选择一个选项
        if (Boolean.FALSE.equals(vote.getVoteMultiple()) && optionIds.size() > 1) {
            throw exception(VOTE_NOT_SUPPORT_MUL);
        }
        long cookie = RequestUtils.resolveIdentityCookie(request, response);
        String ip = RequestUtils.getRemoteAddr(request);
        Long userId = Optional.ofNullable(SecurityFrameworkUtils.getLoginUserId()).orElse(null);
        OffsetDateTime date = vote.getVoteInterval() > 0 ? OffsetDateTime.now().minusDays(vote.getVoteInterval()) : null;
        // 已经投过票
        boolean voted;
        switch (vote.getVoteMode()) {
            case MODE_COOKIE:
                voted = operateService.existOperate(vote.getId(), date, EnumFrontOperate.VOTE, EnumFrontOperateQueryType.COOKIE, request, response);
                break;
            case MODE_IP:
                voted = operateService.existOperate(vote.getId(), date, EnumFrontOperate.VOTE, EnumFrontOperateQueryType.IP, request, response);
                break;
            case MODE_USER:
                if (userId == null) {
                    throw exception(VOTE_NEED_LOGIN);
                }
                voted = operateService.existOperate(vote.getId(), date, EnumFrontOperate.VOTE, EnumFrontOperateQueryType.USER, request, response);
                break;
            default:
                throw exception(VOTE_NOT_SUPPORT_MODE);
        }
        if (voted) {
            throw exception(VOTE_REPEAT);
        }
        service.vote(params.id, optionIds, userId, ip, cookie);
        return CommonResult.success(true);
    }

    private void validateVote(VoteDO vote, List<Long> optionIds) {
        if (optionIds == null) {
            throw exception(VOTE_OPTION_NOT_EXISTS);
        }
        List<Long> voteOptionIds = vote.getItems().stream().map(VoteItemDO::getId).collect(Collectors.toList());
        for (Long optionId : optionIds) {
            if (!voteOptionIds.contains(optionId)) {
                throw exception(VOTE_OPTION_ERROR);
            }
        }
    }

    public static class VoteSubmitDTO {
        @NotNull
        private Long id;
        @NotNull
        private Object items;

        public List<Long> getItemIds() {
            if (items instanceof String[]) {
                return Arrays.stream((String[]) items).map(Long::valueOf).collect(Collectors.toList());
            } else if (items instanceof String) {
                return Collections.singletonList(Long.valueOf((String) items));
            } else {
                throw exception(VOTE_ITEM_PARAM_ERROR);
            }
        }

        public Long getId() {
            return id;
        }

        public void setId(Long id) {
            this.id = id;
        }

        public Object getItems() {
            return items;
        }

        public void setItems(Object items) {
            this.items = items;
        }
    }
}
